/**
 * 
 */
package ac.technion.StreamMania.application.controller.uilisteners;

import ac.technion.gcframework.layers.AbstractApplicationLayer;
import ac.technion.gcframework.layers.ProtocolStack;
import ac.technion.gcframework.layers.system.AckNackLayer;
import ac.technion.gcframework.layers.system.BlockingLayer;
import ac.technion.gcframework.layers.system.IdleDetectingLayer;
import ac.technion.gcframework.layers.system.InconsistentViewManagementLayer;
import ac.technion.gcframework.layers.system.LeakyBucketFlowControlLayer;
import ac.technion.gcframework.layers.system.MessageTranslationLayer;
import ac.technion.gcframework.layers.system.MulticastByUnicastLayer;
import ac.technion.gcframework.layers.system.NoDuplicatesLayer;
import ac.technion.gcframework.layers.system.ReliableDeliveryLayer;
import ac.technion.gcframework.layers.system.SequencingLayer;
import ac.technion.gcframework.layers.system.UDPBroadcastDiscoveryLayer;
import ac.technion.gcframework.layers.system.UDPNetworkLayer;
import ac.technion.gcframework.networking.IPAddress;

/**
 * This class builds the protocol stack for the media streaming application. Most of stack's configuration is defined
 * within as constants.
 * 
 * @author advinsky
 * 
 * Date 31/03/2011
 * 
 */
public class StackBuilder
{
	// The port our stack will be listening on
	private static final int LISTEN_PORT = 5000;
	
	// Maximal size of message that we'll be passing on network (IP specification limits us to 65537)
	private static final int MAX_MESSAGE_SIZE = 64000;
	
	// Member will be considered idle/disconnected if no messages were received from him for this amount of miliseconds
	private static final long IDLE_TIMEOUT_MS = 120000; //3000;
	
	// We broadcast our beacons this often, in milliseconds
	private static final int BEACON_PERIOD_MS = 250; //900
	
	// After receiving at most this many messages from a sender an ACK(nowledgement) is sent back to him
	private static final int MESSAGES_TILL_ACK	= 3;
	
	// Even if we didn't yet receive MESSAGES_TILL_ACK messages, but the oldest unacknowledged one has been received
	// this time ago, we send out an acknowledgement
	private static final long MAX_TIME_TILL_ACK	= 50;
	
	// After not having received an ACK on message for this long we'll consider it lost (and trigger a resend)
	private static final long TIME_TILL_NACK	= 250;
	
	// We expect that duplicate messages if any, might arrive to us in period of time at most this long
	private static final long DUPLICATE_MESSAGE_TIMEOUT = 100000;
	
	public static ProtocolStack buildStack(String broadcastAddress, AbstractApplicationLayer applicationLayer)
	{
		// We'll be using UDP for our network infrastructure (we need the broadcast ability)
		UDPNetworkLayer networkLayer = new UDPNetworkLayer(LISTEN_PORT, MAX_MESSAGE_SIZE, false);
		ProtocolStack stack = new ProtocolStack(networkLayer);
		
		// It's mandatory to place MessageTranslation just above UDP network
		stack.addLayer(new MessageTranslationLayer(networkLayer.getListenPort()));
		
		// We want our protocol stack to have broadcast and multicast ability
		stack.addLayer(new MulticastByUnicastLayer());
		
		// We want to be able to detect when a member has disconnected
		stack.addLayer(new IdleDetectingLayer(IDLE_TIMEOUT_MS));
		
		// We want to be able to automatically discover new members and periodically notify that we're still connected 
		stack.addLayer(new UDPBroadcastDiscoveryLayer(BEACON_PERIOD_MS,
				new IPAddress(broadcastAddress, LISTEN_PORT)));
		
		// We want our communication to be reliable, this is mandatory layer for reliability
		stack.addLayer(new AckNackLayer(
				MESSAGES_TILL_ACK,
				MAX_TIME_TILL_ACK,
				TIME_TILL_NACK));
		
		// The Reliability layer itself
		stack.addLayer(new ReliableDeliveryLayer());
		
		// We don't want' to receive same messages twice in our application
		stack.addLayer(new NoDuplicatesLayer(DUPLICATE_MESSAGE_TIMEOUT));
		
		// We want to receive messages in the same order as sender has sent them
		stack.addLayer(new SequencingLayer());
		
		// We want to receive updates when somebody joins/leaves (view changes) in our application and need this information
		// to be available for other layers as well
		stack.addLayer(new InconsistentViewManagementLayer());
		
		// We have to protect the stack from our application.
		//stack.addLayer(new ThreadSwitchLayer(applicationLayer.getApplicationEventChain()));
		
		stack.addLayer(new LeakyBucketFlowControlLayer(55, 1048576)); //1048576 //524288
		
		stack.addLayer(new BlockingLayer(applicationLayer.getApplicationEventChain()));
		
		// The top-most layer is ours
		stack.addLayer(applicationLayer);
		
		return stack;
	}
}
